package fr.auzaiphe.commun.reseau;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

import fr.auzaiphe.admin.model.AdminModel;
import fr.auzaiphe.commun.Model;
import fr.auzaiphe.commun.View;
import fr.auzaiphe.commun.constantes.ConstantesClientServeur;

/**
 * Démarreur du serveur de chat
 * Une fois démarrée, la classe lance un thread qui écoute les demandes de connexion
 * Lorsqu'une demande arrive on lance un nouveau <code>ChatServerClientHandler</code>
 * pour dialoguer avec le client qui a demandé la connexion.
 * @author Jojo
 */
public class ReseauServer implements Runnable
{
	private AdminModel model;
	private ArrayList<ReseauServeurClientHandler> clients; // Stockage des threads client, pour le Broadcast de message
	private ServerSocket server; 			               // Pour la socket serveur
	private Thread threadAttenteConnexion;	               // Thread d'attente de demande de client
	private ReseauPacketHandler packetHandler;             // Gestionnaire de paquets

	/**
	 * Constructeur
	 * @param pPort pour la mise en place du serveur
	 */
	public ReseauServer( int pPort )
	{
		this.clients = new ArrayList<ReseauServeurClientHandler>();

		try
		{
			System.out.println("Binding to port " + pPort + ", please wait  ...");
			this.server = new ServerSocket(pPort);
		}
		catch ( IOException ioe )
		{
			System.out.println("Can not bind to port " + pPort + ": "+ ioe.getMessage());
		}
	}

	/**
	 * Initialisation du gestionnaire de paquet
	 * @param Model modele qui doit être en réseau
	 * @param View vue qui affiche l'etat du model
	 */
	public void initPacketHandler( Model pModel, View pView )
	{
		this.model = (AdminModel) pModel;
		this.packetHandler = new ReseauPacketHandler( pModel, pView );
		this.start();
		System.out.println("Server started: " + this.server);
	}

	/**
	 * Demarrage du thread d'attente de connexion des clients.
	 */
	@Override
	public void run()
	{
		while (this.threadAttenteConnexion != null)
		{
			try
			{
				System.out.println("Waiting for a client ...");
				this.addThread( this.server.accept() );
			}
			catch (IOException ioe)
			{
				System.out.println("Server accept error: " + ioe);
				this.threadAttenteConnexion.interrupt();
			}
		}
	}

	/**
	 * Mise en route du Thread
	 */
	public void start()
	{
		if (this.threadAttenteConnexion == null)
		{
			this.threadAttenteConnexion = new Thread(this);
			this.threadAttenteConnexion.start();
		}
	}

	/**
	 * Arret du thread
	 */
	public void stop()
	{
		if (this.threadAttenteConnexion != null)
		{
			this.threadAttenteConnexion.interrupt();
			this.threadAttenteConnexion = null;
		}
	}

	/**
	 * Renvoi la position du client dans le tableau de client
	 * en fonction de l'addresse IP du client
	 * @param clientInetAdress adresse Ip du client
	 * @return i indice du client dans le tableau, -1 si client non trouvé
	 */
	private int findClient( InetAddress clientInetAdress )
	{
		for ( int i = 0; i < this.clients.size(); i++ )
		{
			if ( this.clients.get(i).getClientInetAdress().equals( clientInetAdress ) )
			{
				return i;
			}
		}

		return -1;
	}

	/**
	 * Envoie d'un paquet à tous les clients connecté
	 * @param pClientInetAdress
	 * @param pMessage message a envoyer à tout le monde
	 */
	public synchronized void broadcast( Packet pPaquet )
	{
		for (int i = 0; i < this.clients.size(); i++)
		{
			this.clients.get(i).send( pPaquet );
		}
	}

	/**
	 * Envoie du paquet au bon client
	 * @param pDestination
	 * @param pPaquet
	 */
	public synchronized void sendTo( InetAddress pDestination, Packet pPaquet )
	{
		int clientID = this.findClient( pDestination );

		if ( clientID != -1 )
			this.clients.get( clientID ).send( pPaquet );
	}

	/**
	 * Supprime le client passer en parametre de la liste des clients
	 * Il ne pourra plus envoyer de message ni en recevoir.
	 * @param clientInetAdress Adresse IP du client
	 */
	public synchronized void remove( InetAddress clientInetAdress )
	{
		int pos = this.findClient( clientInetAdress );
		if (pos >= 0)
		{
			ReseauServeurClientHandler toTerminate = this.clients.get(pos);
			System.out.println("Removing client thread " + clientInetAdress + " at " + pos);

			this.clients.remove(pos);

			try
			{
				toTerminate.close();
			}
			catch (IOException ioe)
			{
				System.out.println("Error closing thread: " + ioe);
			}
		}
	}

	/**
	 * Creation d'un nouveau Handler pour le client
	 * @param pSocket socket de discussion rendu par accept()
	 */
	private void addThread(Socket pSocket)
	{
		if ( this.clients.size() < ConstantesClientServeur.NBCLIENTMAX )
		{
			System.out.println("Client accepted: " + pSocket);
			ReseauServeurClientHandler client = new ReseauServeurClientHandler( this, pSocket );

			this.clients.add( client );

			try
			{
				client.open();
				client.start();
			}
			catch (IOException ioe)
			{
				System.out.println("Error opening thread: " + ioe);
			}

			// Envoie des paquets à la connexion d'un joueur
			client.send( new PacketInitDataParty( this.model.getIsPartieOn(), this.model.getJoueurs(), this.model.getScenario().getChrono().getSecondesRestantes(), this.model.getScenario().getPrixIndice() ) );
		}
		else
		{
			System.out.println("Client refused: maximum " + ConstantesClientServeur.NBCLIENTMAX + " reached.");
		}
	}

	/**
	 * @return the packetHandler
	 */
	public ReseauPacketHandler getPacketHandler()
	{
		return this.packetHandler;
	}

	/**
	 * @return the server
	 */
	public ServerSocket getServer()
	{
		return this.server;
	}
}